programming4us
           
 
 
Windows Phone

Windows Phone 7 Game Development : Lighting (part 3) - Adding Lighting to Games

- Free product key for windows 10
- Free Product Key for Microsoft office 365
- Malwarebytes Premium 3.7.1 Serial Keys (LifeTime) 2019
9/7/2011 11:21:19 AM

9. Adding Lighting to Games

So that's the theory; now let's take a look at how we implement lighting in our game code. A screenshot from the project is shown in Figure 10.

Figure 10. An illuminated cube and a cylinder rendered by the Lighting example project

9.1. Enabling and Disabling Lighting

The first thing that is needed is to tell the BasicEffect object that we want to use lighting. Without this, all light features will be disabled and ignored, as has been the case in our example projects up until now.

Lighting is enabled by simply setting the LightingEnabled property to true when initializing the effect, as shown in Listing 3.

Example 3. Enabling XNA's lighting feature
_effect.LightingEnabled = true;

Lighting can, of course, be disabled by setting this back to false. This property can be updated anywhere within your game; it isn't restricted just to the Initialize function.

9.2. Light Configuration

Once the lighting system has been switched on, the next step is to configure the lights. We have three directional lights at our disposal, exposed via the DirectionalLight0, DirectionalLight1, and DirectionalLight2 properties of the BasicEffect object. Each of these lights has the following properties that can be used to configure its behavior:

  • DiffuseColor: the diffuse color of the light. Defaults to white.

  • Direction: a Vector3 structure indicating the direction in which the light is pointing. Defaults to (0, −1, 0), straight downward.

  • Enabled: a bool indicating whether this light is switched on or off.

  • SpecularColor: the specular color of the light. Defaults to black to disable specular lighting.

NOTE

All the light colors are represented as Vector3 structures. Remember that you can convert a Color to a Vector3 by calling its ToVector3 method.

The default configuration of the lights is for light 0 to be enabled and the other two lights to be disabled. As the lights point downward by default, this configuration creates an effect similar to the sun shining from directly overhead.

Updating the light settings is very easy because the parameters can be freely updated. To change a light's direction or its colors, or to switch it on and off, simply set the properties as required before rendering your objects.

The code in Listing 4 configures light 0 so that it is directed along the negative z axis. As the user's viewpoint is also looking along the negative z axis in our examples so far, this configuration results in the light illuminating the objects from the camera position.

Example 4. Configuring a white light to shine along the negative z axis
_effect.DirectionalLight0.Enabled = true;
_effect.DirectionalLight0.Direction = new Vector3(0, 0, −1);
_effect.DirectionalLight0.DiffuseColor = Color.White.ToVector3();

For the light itself, this code is all that is required to light up the objects within our scene. However, we haven't done anything to set the normals for our objects yet. Continuing to use the cube from our previous examples, we first modify the class to use the VertexPositionNormalTexture structure for its vertices. After setting the vertex positions as we always have, we now need to set the normal for each vertex. For a cube, the normals all point directly along the x, y, or z axis and so it is easy to set these up manually. Listing 5 shows the beginning of the code to perform this task, taken from the Lighting project's CubeObject class.

Example 5. Setting the cube's vertex normals
// Set the vertex normals
i = 0;
// Front face...
_vertices[i++].Normal = new Vector3(0, 0, 1);
_vertices[i++].Normal = new Vector3(0, 0, 1);
_vertices[i++].Normal = new Vector3(0, 0, 1);
_vertices[i++].Normal = new Vector3(0, 0, 1);
_vertices[i++].Normal = new Vector3(0, 0, 1);
_vertices[i++].Normal = new Vector3(0, 0, 1);
// Back face...
_vertices[i++].Normal = new Vector3(0, 0, −1);
_vertices[i++].Normal = new Vector3(0, 0, −1);
_vertices[i++].Normal = new Vector3(0, 0, −1);
_vertices[i++].Normal = new Vector3(0, 0, −1);
_vertices[i++].Normal = new Vector3(0, 0, −1);
_vertices[i++].Normal = new Vector3(0, 0, −1);
// ... and so on for the remaining faces ...

The cube's class is otherwise unchanged. Running the project displays a cube as shown in Figure 11. You can see that each face of the cube has its own color, determined by the light calculation that we have already explored.

Figure 11. A cube lit using a directional light

Try experimenting with the light and material colors to see how they interact. The light color is set in the project's Initialize function (refer to Listing 7-17), whereas the object material is set against each individual object in its ObjectColor property. Thecube in the example project is added by the ResetGame function, and its color can be modified here.

As additional lights are enabled, XNA has additional work to do to calculate the light for each vertex within the rendered objects. For this reason it is important to disable lights when they are not required. Lighting is a relatively inexpensive calculation, so feel free to get your lights set up exactly how you need them for your game.

9.3. Ambient Light

To use an ambient light, simply set the BasicEffect.AmbientLight property to the required color. All the objects rendered will take the ambient light into account.

9.4. Specular Light

Specular lighting is calculated both from the specular color of the active lights and from the specular material color. As the material colors are specific to each object, we will create new properties in the game framework's MatrixObjectBase class to support specular color for each individual object.

The new properties, SpecularColor (of type Color) and SpecularPower (of type float), mirror the properties within the BasicEffect that control the specular material. These can then be set within each object to control its specular lighting settings.

To apply the specular lighting, the MatrixObjectBase.PrepareEffect is modified to pass the object's values into the BasicEffect object's SpecularColor and SpecularPower properties.

Specular light generally looks at its best when it is white. By all means, experiment with colored specular light, but the effects might not always be natural-looking.

Figure 12 shows another cube with a white specular light and a specular power of 10. You can enable this in the example project by uncommenting the lines in ResetGame that set the two new specular lighting properties. Notice how shiny the cube looks compared with the one without specular lighting. One of the reasons for this shininess is that specular light affects each vertex differently, even if all the vertices of a face point in exactly the same direction. This effect results in a much more dynamic appearance on the rendered objects.

Figure 12. A cube lit using a directional light and specular lighting

Try experimenting with the specular power and see the results. As the power value increases into the tens and hundreds, the cube starts to reflect specular light only when its faces get closer and closer toward the light source as the specular effect becomes more and more tightly focused.

There is one additional BasicEffect property that has an effect on specular lighting: PreferPerPixelLighting. This property defaults to false, which results in the specular component of the lighting model being calculated for each vertex, as we have already discussed. On objects that have large triangles (and therefore have areas of the object where there are no vertices nearby), this can result in some visual artifacts that can detract from the otherwise very attractive looking specular lighting.

The first of these problems can be seen in the left image of Figure 13. The image shows a cube that is facing nearly directly toward the camera and the light source, but is slightly rotated so that the rightmost corner is the only one that is reflecting the specular light. As you can see, the light has a very angular look caused by the fact that only four vertices are being used to display the entire face of the cube. The interpolation is inaccurate due to this small number of color points.

The image on the right is of the exact same object and lighting configuration, except that PreferPerPixelLighting has been switched on. This property instructs XNA to calculate the specular lighting for each individual pixel that it renders, rather than just for the vertices. As will be clearly seen, the reflection looks much better: the angular lines have all disappeared, leaving a perfect round highlight in its place.

Figure 13. Two cubes, without (left) and with (right) per pixel lighting enabled

The second problem with specular lighting also occurs on objects with large triangles, but primarily affects specular light that is very tightly focused. Figure 14 shows two images of a cube that is directly facing toward the camera and the light source. The specular power has been set to 1000 for a very focused effect. Because the effect is so small, it doesn't reach any of the vertices at all. As a result, the specular light has no impact on the face. On the right is the same scene with per pixel lighting enabled. As each pixel then has its specular light individually calculated, the specular light effect clearly appears within the face.

Figure 14. Two cubes, without and with per pixel lighting enabled with a high specular focus

Of course, as you might expect, there is a downside to per pixel lighting. Because the specular component needs to be calculated for each individual pixel as opposed to each vertex, it has a much higher processing requirement. Consider the cubes in Figure 7-30: the visible face consists of more than 30,000 pixels, as compared with only 6 vertices.

Per pixel lighting should therefore be used sparingly. If you have an object that is not using specular light, has no large faces, has a low specular power, or doesn't exhibit either of the problems discussed here, you will probably find a performance benefit from leaving it disabled. Experiment and find which setting provides the best balance between appearance and performance for your game.

9.5. Emissive Light

The emissive light for rendered objects is also most usefully set for each individual object, so we will add a new property for this to the MatrixObjectBase class just as we did for the specular material color.

The EmissiveColor property (of type Color) can then be set within each object to control its emissive lighting settings and is applied in the MatrixObjectBase.PrepareEffect, which passes its value into the BasicEffect object's EmissiveColor property.

9.6. The Standard Lighting Rig

The lighting properties of the BasicEffect object give you a great deal of freedom to set up your lighting system in whatever way you want, but XNA has one additional feature that you might find useful in your games: the standard lighting rig.

I will allow Shawn Hargreaves, one of the Microsoft's XNA Framework developers, to explain with the following text from his blog (which, incidentally, is a fantastically useful resource for XNA programming and can be found at http://blogs.msdn.com/b/shawnhar/):

Many years ago photographers discovered that a single light was not enough to make their subjects look good. Instead, they use three.

The key light is the brightest, and provides the main illumination and shadows. This will typically be positioned to match a real light source such as an overhead lamp, a window, or the sun for an outdoor scene.

The fill light is dimmer, and usually angled at 90 degrees to the key. This is used to soften the shadows, adding shading and definition to areas that would otherwise be solid black.

Finally, the back light is positioned behind the character, facing toward the camera. This illuminates only the silhouette edges, helping the character stand out against the background.

Because this is potentially a very useful light configuration, it can be applied to your 3D game world by simply calling the BasicEffect.EnableDefaultLighting function. In practice, it might provide a useful set of lights, it might provide a useful basis but require a little subsequent modification, or it might not be suitable for your game at all. Give it a try and see what kind of results it provides; you might just like it.

9.7. Programmatic Calculation of Normals

Earlier in this section, we explored the calculations required to automatically calculate the normals for a triangle within a 3D object. As a final lighting-related addition to the game framework, let's add a function that will calculate the normals automatically.

This will be relatively basic, and will operate within the following restrictions:

  • It will only generate normals that are the same for all vertices of a triangle, so no smoothing using normal interpolation will be supported.

  • It will assume that each vertex will be either used only once or that all of its uses will have the same vertex normal. The code could potentially be enhanced to average out multiple uses of the same vertex, but this enhancement is left as an exercise for the reader.

  • It will support only triangle lists.

As we have two different ways of rendering triangles (a simple list of triangles or using vertex indices), we will create two corresponding functions that process data in these two formats. To reduce the amount of code, we will set the two functions up so that one simply calls into the other, allowing all the calculation to be put into just a single function.

The easiest way to implement this efficient code approach is to get the version that takes a simple list of triangles (without vertex indices) to build a corresponding index array. Once this is done, the vertices and the fabricated indices can be passed into the other function to calculate on its behalf.

Generating indices for an unindexed triangle list is very simple: each triangle is formed from the next three vertices in the list, so the indices are just a sequence of incremental numbers. The first triangle is formed from indices 0, 1, and 2; the second triangle from indices 3, 4, and 5; the third from indices 6, 7, and 8; and so on.

The code for the function that handles unindexed vertices, named CalculateVertexNormals and added to the MatrixObjectBase class, can be seen in Listing 6. It creates an array for the indices whose length is equal to the number of vertices. The array is then filled with sequential numbers starting from 0, and the vertices and constructed indices array are passed into a second overload of the same function to actually generate the normals, which we will examine in a moment.

Example 6. Generating indices for an unindexed triangle list
public void CalculateVertexNormals(VertexPositionNormalTexture[] vertices)
{
short[] indices;
short i;

// Build an array that allows us to treat the vertices as if they were indexed.
// As the triangles are drawn sequentially, the indexes are actually just
// an increasing sequence of numbers: the first triangle is formed from
// vertices 0, 1 and 2, the second triangle from vertices 3, 4 and 5, etc.

// First create the array with an element for each vertex
indices = new short[vertices.Length];

// Then set the elements within the array so that each contains
// the next sequential vertex index
for (i = 0; i < indices.Length; i++)
{
indices[i] = i;
}

// Finally delegate to the other overload to do the work
CalculateVertexNormals(vertices, indices);
}


The second overload of the function accepts two parameters: the vertex array and the index array. This version of the function would be used directly if you are working with indexed vertices, or will otherwise be called from the code in Listing 7-19 if the vertices are unindexed.

The second overload works through the vertices in the array, using the indices to determine which vertices are used to form each triangle within the object. Once the vertices of each triangle have been determined, their vectors are calculated and stored in the va and vb variables. Their cross product is then calculated in order to determine the triangle's normal, and the resulting vector is normalized. The normal vector is then written into all three of the vertices that formed the triangle.

Note that as we are updating the vertex array that was passed in as a parameter; there is no need to return anything from this function. The normals will be written "in place" into the existing vertices.

The code to calculate the normals is shown in Listing 7.

Example 7. Calculating the normals for an indexed triangle list
                                                                        short[] indices)
{
// Vectors to describe the relationships between the vertices of the triangle
// being processed
Vector3 vectora;
Vector3 vectorb;
// The resulting normal vector
Vector3 normal;

// Loop for each triangle (each triangle uses three indices)
for (int index = 0; index < indices.Length; index += 3)
{
// Create the a and b vectors from the vertex positions
// First the a vector from vertices 2 and 1
vectora = vertices[index + 2].Position - vertices[index + 1].Position;
// Next the b vector from vertices 1 and 0
vectorb = vertices[index + 1].Position - vertices[index + 0].Position;

// Calculate the normal as the cross product of the two vectors
normal = Vector3.Cross(vectora, vectorb);

// Normalize the normal
normal.Normalize();

// Write the normal back into all three of the triangle vertices
vertices[index].Normal = normal;
vertices[index+1].Normal = normal;
vertices[index+2].Normal = normal;
}
}


This function can simply be called after the vertex positions for an object have been calculated. If you look at the CubeObject class in the Lighting example project, you will find that this can be used in place of the code that manually provides the normals. Try commenting out all the BuildVertices code that sets the vertex normals (from the "Set the vertex normals" comment on to the end of the function) and instead enable the call to CalculateVertexNormals. The end result is identical, but with a lot less code and a lot less effort.

Other -----------------
- Windows Phone 7 : User Interface - Using Panorama and Pivot Controls
- Windows Phone 7 : User Interface - Localizing Your Application
- User Interface : Using the Windows Phone 7 Predefined Styles
- Handling Input on Windows Phone 7 : Touch Input (part 3) - Multi-Point Touch
- Handling Input on Windows Phone 7 : Touch Input (part 2) - Raw Touch with Mouse Events
- Handling Input on Windows Phone 7 : Touch Input (part 1) - Single-Point Touch
- Handling Input on Windows Phone 7 : The Keyboard
- User Interface : Customizing the Soft Input Panel Keyboard to Accept Only Numbers
- User Interface : Detecting Changes in the Theme Template
- Developing for Windows Phone and Xbox Live : Multiplayer Games (part 6) - Searching for an Available Network Session
- Developing for Windows Phone and Xbox Live : Multiplayer Games (part 5) - Searching for an Available Network Session
- Developing for Windows Phone and Xbox Live : Multiplayer Games (part 4) - Building a Game Lobby
- Developing for Windows Phone and Xbox Live : Multiplayer Games (part 3) - Creating a Network Session
- Developing for Windows Phone and Xbox Live : Multiplayer Games (part 2) - Main Menu and State Management
- Developing for Windows Phone and Xbox Live : Multiplayer Games (part 1) - Getting Ready for Networking Development
- User Interface : Using the ApplicationBar Control
- User Interface : Creating an Animated Splash Screen
- Windows Phone 7 Game Development : The World of 3D Graphics - Vertex and Index Buffers
- Windows Phone 7 Game Development : The World of 3D Graphics - Hidden Surface Culling
- Windows Phone 7 Game Development : The World of 3D Graphics - The Depth Buffer
 
 
 
Top 10
 
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
programming4us programming4us